package com.chenfan.ccp.plug.business.aspect;

import com.chenfan.ccp.plug.business.dto.FillFieldValueDto;
import com.chenfan.ccp.plug.business.send.AbstractLogSender;
import com.chenfan.ccp.plug.business.annotation.LogProperty;
import com.chenfan.ccp.plug.business.annotation.LogRecord;
import com.chenfan.ccp.plug.business.constants.enums.DicHandler;
import com.chenfan.ccp.plug.business.entity.ActionLog;
import com.chenfan.ccp.plug.business.parser.LogRecordContext;
import com.chenfan.ccp.plug.business.parser.TemplateLogExpressionEvaluator;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.encoder.org.apache.commons.lang3.StringUtils;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.expression.EvaluationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.stream.Collectors;

import static com.chenfan.ccp.plug.business.annotation.LogProperty.LogFieldTypeEnum.DIC;
import static com.chenfan.ccp.plug.business.constants.Constants.*;

/**
 * 需求思考考虑，将业务日志拆分为行为日志，和数据操作日志
 *
 * @author xutong
 */
@Slf4j
public class LogRecordMethodInterceptor implements MethodInterceptor {
    private final AbstractLogSender logSender;
    private final TemplateLogExpressionEvaluator templateLogExpressionEvaluator;

    public LogRecordMethodInterceptor(AbstractLogSender logSender, TemplateLogExpressionEvaluator templateLogExpressionEvaluator) {
        this.logSender = logSender;
        this.templateLogExpressionEvaluator = templateLogExpressionEvaluator;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        LogRecordContext.addThreadLocal(new HashMap<>());
        Object proceed;
       try {
            proceed = methodInvocation.proceed();
       }catch (Exception e){
           LogRecordContext.clearThreadLocal();
           throw e;
       }
        try {
            Object[] args = methodInvocation.getArguments();
            Method method = methodInvocation.getMethod();
            Class<?> targetClass = methodInvocation.getThis().getClass();
            LogRecord logRecord = AnnotatedElementUtils.findMergedAnnotation(method, LogRecord.class);
            EvaluationContext evaluationContext = templateLogExpressionEvaluator
                    .createEvaluationContext(methodInvocation.getThis(), method, args, proceed, BLANK_STRING);
            ActionLog actionLog = new ActionLog();
            setSpelParseResult2Field(targetClass, logRecord.bizType(), evaluationContext, a -> actionLog.setBizType(a + ""));
            setSpelParseResult2Field(targetClass, logRecord.actionName(), evaluationContext, a -> actionLog.setActionName(a + ""));
            actionLog.setOperationTime(new Date());
            setSpelParseResult2Field(targetClass, logRecord.bizNo(), evaluationContext, a -> actionLog.setBizNo(a + ""));
            List<FillFieldValueDto> fieldValueDtos = Arrays.stream(logRecord.recordFields()).map(logProperty ->
                            getFieldValueDto(targetClass, evaluationContext, logProperty.field(), logProperty))
                    .collect(Collectors.toList());
            if (fieldValueDtos == null) {
                fieldValueDtos = new LinkedList<>();
            }
            //解析发送,参数中的日志注解
            if (logRecord.parseEntityIndex().length > 0 && args != null && args.length > 0) {
                for (int i = 0; i < logRecord.parseEntityIndex().length; i++) {
                    try {
                        Object arg = args[logRecord.parseEntityIndex()[i]];
                        if (arg instanceof Collection) {
                            Collection list = Collection.class.cast(arg);
                            for (Object obj : list) {
                                List<FillFieldValueDto> dtos = parseSingleEntityAndSendLogs(targetClass, evaluationContext, obj);
                                if (dtos != null) {
                                    fieldValueDtos.addAll(dtos);
                                }
                            }
                        } else {
                            List<FillFieldValueDto> dtos = parseSingleEntityAndSendLogs(targetClass, evaluationContext, arg);
                            if (dtos != null) {
                                fieldValueDtos.addAll(dtos);
                            }
                        }
                    } catch (Exception e) {
                        log.warn("非法的需要参数被解析的索引====={},跳出参数解析", i);
                        break;
                    }
                }
            }
            doRecordLogBaseLogRecord(actionLog, fieldValueDtos, evaluationContext);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        LogRecordContext.clearThreadLocal();
        return proceed;
    }


    private List<FillFieldValueDto> parseSingleEntityAndSendLogs(Class targetClass, EvaluationContext evaluationContext, Object arg) {
        List<FillFieldValueDto> dtos = Arrays.stream(arg.getClass().getDeclaredFields()).filter(field -> field.getAnnotation(LogProperty.class) != null).map(field -> {
            String name = field.getName();
            LogProperty annotation = field.getAnnotation(LogProperty.class);
            if (StringUtils.isNotBlank(annotation.field())) {
                name = annotation.field();
            }
            //解析对象内实体不支持遍历
            FillFieldValueDto dto = getFieldValueDtoBaseObj(targetClass, evaluationContext, name,field.getName(), annotation, false, arg);
            return dto;
        }).collect(Collectors.toList());
        return dtos;
    }

    /**
     * 基于实体解析更新后的值，不支持查询更新前的值，#开头
     *
     * @param targetClass
     * @param evaluationContext
     * @param name
     * @param annotation
     * @param foreach
     * @param target
     * @return
     */
    private FillFieldValueDto getFieldValueDtoBaseObj(Class targetClass, EvaluationContext evaluationContext,  String name,String fieldName, LogProperty annotation, boolean foreach, Object target) {
        FillFieldValueDto dto = FillFieldValueDto.builder().build();
        dto.setTargetClass(targetClass);
        dto.setFieldType(annotation.fieldType().name());
        dto.setField(name);
        dto.setBeforeValue(annotation.beforeValue());

        parseSingleElAndSet(target, "#" + fieldName, dto::setAfterValue);
        parseSingleElAndSet(target, annotation.subBizNo(), dto::setSubBizNo);
        dto.setEvaluationContext(evaluationContext);
        dto.setForeach(foreach);
        dto.setDictionary(annotation.dictionary());
        dto.setDic(annotation.dic());
        return dto;
    }

    /**
     * 解析对象内的表达式
     *
     * @param target
     * @param expression
     * @param consumer
     */
    private void parseSingleElAndSet(Object target, String expression, Consumer<String> consumer) {
        String field = "";
        if (StringUtils.startsWith(expression, "#")) {
            String substring = expression.substring(1);
            try {
                Field declaredField = target.getClass().getDeclaredField(substring);
                declaredField.setAccessible(true);
                field = declaredField.get(target) + "";
            } catch (NoSuchFieldException | IllegalAccessException e) {
                field = expression;
            }
        } else {
            field = expression;
        }
        consumer.accept(field);
    }

    private FillFieldValueDto getFieldValueDto(Class targetClass, EvaluationContext evaluationContext, String name, LogProperty annotation) {
        FillFieldValueDto dto = FillFieldValueDto.builder().build();
        dto.setTargetClass(targetClass);
        dto.setFieldType(annotation.fieldType().name());
        dto.setField(name);
        dto.setAfterValue(annotation.afterValue());
        dto.setBeforeValue(annotation.beforeValue());
        dto.setSubBizNo(annotation.subBizNo());
        dto.setEvaluationContext(evaluationContext);
        dto.setForeach(annotation.foreach());
        dto.setDictionary(annotation.dictionary());
        dto.setDic(annotation.dic());
        return dto;
    }


    private void doRecordLogBaseLogRecord(ActionLog actionLog, List<FillFieldValueDto> fieldValueDtos, EvaluationContext evaluationContext) {

        if (fieldValueDtos.size() > 0) {
            ArrayList<ActionLog.ActionData> actionList = new ArrayList<>(fieldValueDtos.size());
            actionLog.setActionList(actionList);
            for (FillFieldValueDto dto : fieldValueDtos) {
                fillFieldValue(dto, actionList);
            }
            if (actionList.isEmpty()) {
                return;
            }
            String requestURI = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest().getRequestURI();
            if (StringUtils.isBlank(requestURI)) {
                log.warn("非法的请求url，请提供请求url");
            }

            logSender.send(actionLog, requestURI);
        }
    }

    @SneakyThrows
    private void fillFieldValue(FillFieldValueDto fieldValueDto, ArrayList<ActionLog.ActionData> actionList) {
        //解析生成日志detail
        ActionLog.ActionData data = new ActionLog.ActionData();
        data.setFieldName(fieldValueDto.getField());
        data.setFieldType(fieldValueDto.getFieldType());
        String fieldType = fieldValueDto.getFieldType();
        EvaluationContext evaluationContext = fieldValueDto.getEvaluationContext();
        //如果是字典类型，解析字典
        if (fieldValueDto.isForeach()) {
            String rootName = getRootName(fieldValueDto.getBeforeValue(), fieldValueDto.getAfterValue(),fieldValueDto.getSubBizNo());
            if (StringUtils.isNotBlank(rootName)) {
                Object lookupVariable = templateLogExpressionEvaluator.parseExpression(rootName,
                        new AnnotatedElementKey(LogProperty.class, fieldValueDto.getTargetClass()),
                        evaluationContext);
                if (lookupVariable == null) {
                    return;
                }
                if (lookupVariable instanceof List) {
                    List list = List.class.cast(lookupVariable);
                    for (int i = 0; i < list.size(); i++) {
                        FillFieldValueDto build = FillFieldValueDto
                                .builder()
                                .targetClass(fieldValueDto.getTargetClass())
                                .field(parseForeachTemplate(fieldValueDto.getField(),i))
                                .fieldType(fieldValueDto.getFieldType())
                                .afterValue(parseForeachTemplate(fieldValueDto.getAfterValue(), i))
                                .beforeValue(parseForeachTemplate(fieldValueDto.getBeforeValue(), i))
                                .subBizNo(parseForeachTemplate(fieldValueDto.getSubBizNo(), i))
                                .evaluationContext(evaluationContext)
                                .foreach(false)
                                .dictionary(fieldValueDto.getDictionary())
                                .dic(fieldValueDto.getDic())
                                .build();
                        this.fillFieldValue(build, actionList);
                    }
                }
            }
            return;
        }
        setSpelParseResult2Field(fieldValueDto.getTargetClass(), fieldValueDto.getField(), evaluationContext, data::setFieldName);
        setSpelParseResult2Field(fieldValueDto.getTargetClass(), fieldValueDto.getAfterValue(), evaluationContext, data::setAfterValue);
        setSpelParseResult2Field(fieldValueDto.getTargetClass(), fieldValueDto.getBeforeValue(), evaluationContext, data::setBeforeValue);
        setSpelParseResult2Field(fieldValueDto.getTargetClass(), fieldValueDto.getSubBizNo(), evaluationContext, a -> data.setSubBizNo(a + ""));
        data.setSurfaceAfterValue(data.getAfterValue());
        if (DIC.name().equals(fieldType) && evaluationContext != null) {
            if (Object.class.equals(fieldValueDto.getDic())) {
                Object dic = evaluationContext.lookupVariable(fieldValueDto.getDictionary());
                if (!Objects.isNull(dic) && dic instanceof Map) {
                    Map map = Map.class.cast(dic);
                    data.setDictionary(map);
                    data.setSurfaceAfterValue(map.get(data.getAfterValue()));
                }
            } else if (DicHandler.class.isAssignableFrom(fieldValueDto.getDic())){
                DicHandler dicHandler = (DicHandler) fieldValueDto.getDic().newInstance();
                Map<String, String> map = dicHandler.dicMap();
                data.setDictionary(map);
                data.setSurfaceAfterValue(map.get(data.getAfterValue()));
            }
        }
        if (StringUtils.equals(data.getBeforeValue() + "", data.getAfterValue() + "")) {
            return;
        }
        actionList.add(data);

    }


    private void setSpelParseResult2Field(Class targetClass, Object value, EvaluationContext evaluationContext, Consumer<Object> consumer) {
        consumer.accept(value);
        if (StringUtils.startsWithAny(value.toString(), "#", "$")) {
            //暂时不记录，错误日志所以错误日志为空
            try {
                value = templateLogExpressionEvaluator.parseExpression(value.toString(),
                        new AnnotatedElementKey(LogProperty.class, targetClass),
                        evaluationContext);
                consumer.accept(value);
            } catch (Exception e) {
                //解析失败不做任何操作
                log.error("spel表达式解析失败{},consumer====={}", value, consumer);
            }
        }
    }

    private String parseForeachTemplate(String afterValue, int i) {
        return afterValue.replaceAll(FOREACH_PLACEHOLDER, i + "");
    }

    /**
     * 从表达式中获取根对象
     * 获取需要被遍历的对象的名字
     * 暂时不支持笛卡尔积的遍历，仅支持对一个对象的遍历
     */
    private String getRootName(String... expressions) {
        for (String expression : expressions) {
            String group = getGroup(expression);
            if (group != null) {
                return group;
            }
        }
        log.warn("[解析遍历][非法的遍历指令]spel中不存在占位符{}", FOREACH_PLACEHOLDER);
        return "";
    }

    /**
     * 解析数组对象名字
     * @param beforeValue
     * @return
     */
    public static String getGroup(String beforeValue) {
        Matcher foreachMatch = ROOT_MATCHER_FOREACH.matcher(beforeValue);
        if (foreachMatch.find()) {
            String prefix = foreachMatch.group();
            Matcher matcher = ROOT_MATCHER.matcher(prefix);
            if(matcher.find()) {
                String group = matcher.group();
                return group.substring(0, group.length() - 1);
            }
        }
        return null;
    }
}
